/*
 *This file retrieved from http://pholia.tdi.informatik.uni-frankfurt.de/~philipp/software/dssim.cpp
 *  on 20130106
 *A different implementation is available at
 *  http://nma.web.nitech.ac.jp/fukushima/opencv/ssim/ssim-e.html
 */

/*
	This DSSIM program has been created by Philipp Klaus Krause based on
	Rabah Mehdi's C++ implementation of SSIM (http://mehdi.rabah.free.fr/SSIM).
	Originally it has been created for the VMV '09 paper
	"ftc - floating precision texture compression" by Philipp Klaus Krause.

	The latest version of this program can probably be found somewhere at
	http://www.colecovision.eu.

	It can be compiled using g++ -I/usr/include/opencv -lcv -lhighgui dssim.cpp
	Make sure OpenCV is installed (e.g. for Debian/ubuntu: apt-get install
	libcv-dev libhighgui-dev).

	DSSIM is described in
	"Structural Similarity-Based Object Tracking in Video Sequences" by Loza et al.
	however setting all Ci to 0 as proposed there results in numerical instabilities.
	Thus this implementation used the Ci from the SSIM implementation.
	SSIM is described in
	"Image quality assessment: from error visibility to structural similarity" by Wang et al.
*/

/*
	Copyright (c) 2005, Rabah Mehdi <mehdi.rabah@gmail.com>

	Feel free to use it as you want and to drop me a mail
	if it has been useful to you. Please let me know if you enhance it.
	I'm not responsible if this program destroy your life & blablabla :)

	Copyright (c) 2009, Philipp Klaus Krause <philipp@colecovision.eu>

	Permission to use, copy, modify, and/or distribute this software for any
	purpose with or without fee is hereby granted, provided that the above
	copyright notice and this permission notice appear in all copies.

	THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
	WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
	MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
	ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
	WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
	ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
	OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

#include <cv.h>	
#include <highgui.h>
#include <fstream>
#include <iostream>
#include <stdexcept>
#include <algorithm>

using namespace std;

double dssim( const IplImage *original_img, const IplImage *test_img )
{
	
	// default settings
	double C1 = 6.5025, C2 = 58.5225;

	IplImage
		*img1=NULL, *img2=NULL, *img1_img2=NULL,
		*img1_sq=NULL, *img2_sq=NULL,
		*mu1=NULL, *mu2=NULL,
		*mu1_sq=NULL, *mu2_sq=NULL, *mu1_mu2=NULL,
		*sigma1_sq=NULL, *sigma2_sq=NULL, *sigma12=NULL,
		*ssim_map=NULL, *temp1=NULL, *temp2=NULL, *temp3=NULL;
/*
    ifstream file1strm( file1 );
    istream::pos_type pos0 = file1strm.tellg();
    file1strm.seekg( 0, ios::end );
    istream::pos_type pos1 = file1strm.tellg();
    const size_t file1size = 0 + pos1 - pos0;
    file1strm.seekg( 0, ios::beg );

    ifstream file2strm( file2 );
    pos0 = file1strm.tellg();
    file2strm.seekg( 0, ios::end );
    pos1 = file1strm.tellg();
    const size_t file2size = 0 + pos1 - pos0;
    file2strm.seekg( 0, ios::beg );

    vector<char> file1data( file1size ), file2data( file2size );
    file1strm.read( &file1data[0], file1size );
    file2strm.read( &file2data[0], file2size );

    CvMat file1buf = cvMat(1, file1size, CV_8UC1, &file1data[0]);
    original_img = cvDecodeImage( &file1buf, CV_LOAD_IMAGE_ANYCOLOR );
    CvMat file2buf = cvMat(1, file2size, CV_8UC1, &file2data[0]);
    img2_temp = cvDecodeImage( &file2buf, CV_LOAD_IMAGE_ANYCOLOR );
*/

	/***************************** INITS **********************************/
    if(!original_img)
      throw std::runtime_error( "Invalid input original image file" );

    if(!test_img)
        throw std::runtime_error( "Could not read test image file" );

    int x=original_img->width, y=original_img->height;
    int nChan=original_img->nChannels, d=IPL_DEPTH_32F;
	CvSize size = cvSize(x, y);

	img1 = cvCreateImage( size, d, nChan);
	img2 = cvCreateImage( size, d, nChan);

    cvConvert(original_img, img1);
    cvConvert(test_img, img2);

	
	img1_sq = cvCreateImage( size, d, nChan);
	img2_sq = cvCreateImage( size, d, nChan);
	img1_img2 = cvCreateImage( size, d, nChan);
	
	cvPow( img1, img1_sq, 2 );
	cvPow( img2, img2_sq, 2 );
	cvMul( img1, img2, img1_img2, 1 );

	mu1 = cvCreateImage( size, d, nChan);
	mu2 = cvCreateImage( size, d, nChan);

	mu1_sq = cvCreateImage( size, d, nChan);
	mu2_sq = cvCreateImage( size, d, nChan);
	mu1_mu2 = cvCreateImage( size, d, nChan);
	

	sigma1_sq = cvCreateImage( size, d, nChan);
	sigma2_sq = cvCreateImage( size, d, nChan);
	sigma12 = cvCreateImage( size, d, nChan);

	temp1 = cvCreateImage( size, d, nChan);
	temp2 = cvCreateImage( size, d, nChan);
	temp3 = cvCreateImage( size, d, nChan);

	ssim_map = cvCreateImage( size, d, nChan);
	/*************************** END INITS **********************************/


	//////////////////////////////////////////////////////////////////////////
	// PRELIMINARY COMPUTING
	cvSmooth( img1, mu1, CV_GAUSSIAN, 11, 11, 1.5 );
	cvSmooth( img2, mu2, CV_GAUSSIAN, 11, 11, 1.5 );
	
	cvPow( mu1, mu1_sq, 2 );
	cvPow( mu2, mu2_sq, 2 );
	cvMul( mu1, mu2, mu1_mu2, 1 );


	cvSmooth( img1_sq, sigma1_sq, CV_GAUSSIAN, 11, 11, 1.5 );
	cvAddWeighted( sigma1_sq, 1, mu1_sq, -1, 0, sigma1_sq );
	
	cvSmooth( img2_sq, sigma2_sq, CV_GAUSSIAN, 11, 11, 1.5 );
	cvAddWeighted( sigma2_sq, 1, mu2_sq, -1, 0, sigma2_sq );

	cvSmooth( img1_img2, sigma12, CV_GAUSSIAN, 11, 11, 1.5 );
	cvAddWeighted( sigma12, 1, mu1_mu2, -1, 0, sigma12 );
	

	//////////////////////////////////////////////////////////////////////////
	// FORMULA

	// (2*mu1_mu2 + C1)
	cvScale( mu1_mu2, temp1, 2 );
	cvAddS( temp1, cvScalarAll(C1), temp1 );

	// (2*sigma12 + C2)
	cvScale( sigma12, temp2, 2 );
	cvAddS( temp2, cvScalarAll(C2), temp2 );

	// ((2*mu1_mu2 + C1).*(2*sigma12 + C2))
	cvMul( temp1, temp2, temp3, 1 );

	// (mu1_sq + mu2_sq + C1)
	cvAdd( mu1_sq, mu2_sq, temp1 );
	cvAddS( temp1, cvScalarAll(C1), temp1 );

	// (sigma1_sq + sigma2_sq + C2)
	cvAdd( sigma1_sq, sigma2_sq, temp2 );
	cvAddS( temp2, cvScalarAll(C2), temp2 );

	// ((mu1_sq + mu2_sq + C1).*(sigma1_sq + sigma2_sq + C2))
	cvMul( temp1, temp2, temp1, 1 );

	// ((2*mu1_mu2 + C1).*(2*sigma12 + C2))./((mu1_sq + mu2_sq + C1).*(sigma1_sq + sigma2_sq + C2))
	cvDiv( temp3, temp1, ssim_map, 1 );


	CvScalar index_scalar = cvAvg( ssim_map );

	double dssim = index_scalar.val[0];
	for(unsigned int i = 1; i < nChan; i++)
		dssim = min(dssim, index_scalar.val[i]);
	dssim = 1.0 / dssim - 1;

    cvReleaseImage( &img1 );
    cvReleaseImage( &img2 );
    cvReleaseImage( &img1_sq );
    cvReleaseImage( &img2_sq );
    cvReleaseImage( &img1_img2 );
    cvReleaseImage( &mu1 );
    cvReleaseImage( &mu2 );
    cvReleaseImage( &mu1_sq );
    cvReleaseImage( &mu2_sq );
    cvReleaseImage( &mu1_mu2 );
    cvReleaseImage( &sigma1_sq );
    cvReleaseImage( &sigma2_sq );
    cvReleaseImage( &sigma12 );
    cvReleaseImage( &temp1 );
    cvReleaseImage( &temp2 );
    cvReleaseImage( &temp3 );
    cvReleaseImage( &ssim_map );

//	std::cout.precision(3);
//	std::cout << fixed << dssim << "\n";

	// if you use this code within a program
	// don't forget to release the IplImages
    return dssim;
}//double dssim( const char *file1, const char *file2 )

